package br.com.spam.pipeline.instruction.i;

import java.util.Map;

import br.com.spam.pipeline.core.Configuration;
import br.com.spam.pipeline.core.ControlSignal;
import br.com.spam.pipeline.core.Processor;
import br.com.spam.pipeline.core.RegisterDataSource;
import br.com.spam.pipeline.instruction.ITypeInstruction;
import br.com.spam.pipeline.instruction.RsRtLoader;
import br.com.spam.pipeline.instruction.r.SUB;

public abstract class BTypeInstruction extends ITypeInstruction
{
	public static final int ID = 101;
	private RsRtLoader loader;
	private Boolean prediction;
	private int nonBranchDestination;

	public BTypeInstruction(String instruction, Processor processor)
	{
		super(instruction, processor);
		this.loader = new RsRtLoader(this.rs(), this.rt(), this);
	}

	@Override
	public String getInstructionAsString()
	{
		return String.format("I%d: %s %s, %s, %d", this.index(), this.name(), this.rs().name(),
				this.rt().name(), this.immediate());
	}

	@Override
	public boolean instructionFetch()
	{
		if (Configuration.preditorDesvio)
		{
			this.nonBranchDestination = this.processor.getPC().getPersistedValue() + 4;
			if (this.prediction = this.processor.preditorDesvio().getPrediction(this.index()))
			{
				this.processor.getPC().write(this.getBranchDestination(),
						RegisterDataSource.JustResolvedJump);
			}
		}
		else
		{
			this.processor.getPC().lock();
		}

		return true;
	}

	@Override
	protected boolean afterInstructionDecode()
	{
		return this.loader.instructionDecodeHook();
	}

	@Override
	public boolean execute()
	{
		this.loader.executionHook();

		boolean shouldBranch = this.shouldBranch(this.loader.rsValue(), this.loader.rtValue());

		if (Configuration.preditorDesvio)
		{
			this.processor.preditorDesvio().addPrediction(this.index(), shouldBranch);
			if (shouldBranch != this.prediction)
			{
				this.processor.getPipelineSimulator().resetPhasesDueToWrongPrediction();
				this.processor.getPC().write(
						shouldBranch ? this.getBranchDestination() : this.nonBranchDestination,
						RegisterDataSource.JustResolvedJump);
			}
		}
		else
		{
			if (shouldBranch)
			{
				this.processor.getPC().write(this.getBranchDestination(),
						RegisterDataSource.JustResolvedJump);
			}
			else
			{
				this.processor.getPC().release();
			}
		}
		return true;
	}
	protected abstract boolean shouldBranch(int rsValue, int rtValue);

	protected abstract int getBranchDestination();

	protected abstract int getBranchControlSignal();

	@Override
	public final void generateControlSignals(Map<ControlSignal, Object> controlSignals)
	{
		controlSignals.put(ControlSignal.ALUSrcB, 0);
		controlSignals.put(ControlSignal.RegWrite, 0);
		controlSignals.put(ControlSignal.MemWrite, 0);
		controlSignals.put(ControlSignal.Branch, 1);
		controlSignals.put(ControlSignal.Jump, 0);
		controlSignals.put(ControlSignal.BranchCondition, this.getBranchControlSignal());
		controlSignals.put(ControlSignal.ALUOp, SUB.ALUOpCode);
	}
}
